﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Godot.core
{
    using SysMethodInfo = System.Reflection.MethodInfo;
    using SysBindingFlags = System.Reflection.BindingFlags;
    public static class Misc
    {
        public static class Version
        {
            public const int PACK_VERSION = 0;
            public const int VERSION_MAJOR = 1;
            public const int VERSION_MINOR = 0;
            public const int VERSION_REVISION = 3917;
        }

        #region Comparers
        public class StringNameComparer : IEqualityComparer<StringName>
        {

            public bool Equals(StringName x, StringName y)
            {
                return ((String)x).Equals((String)y);
            }

            public int GetHashCode(StringName obj)
            {
                return obj.GetHashCode();
            }
        }

        public static Dictionary<Type, object> register_comparer = new Dictionary<Type, object>()
        {
            {  typeof(StringName), new StringNameComparer() },
        };
        #endregion

        #region Maths
        //=======================================================================================
        public static double db2linear(double p_linear)
        {
            return Math.Exp(p_linear * 0.11512925464970228420089957273422);
        }

        public static double linear2db(double p_linear) 
        {
		    return Math.Log(p_linear ) * 8.6858896380650365530225783783321;
	    }

        public static int decimals(double p_step)
        {
            int max = 4;
	        int i = 0;
	        while((p_step - Math.Floor(p_step)) != 0.0 && max != 0) 
            {
		
		        p_step *= 10.0;
		        max--;
		        i++;
	        }
	
	        return i;
        }

        public static double dectime(double p_value,double p_amount, double p_step) 
        {
	        int sgn = p_value < 0 ? -1 : 1;
	        double val = Math.Abs(p_value);
	        val -= p_amount * p_step;
	        if (val < 0.0)
		        val = 0.0;
	        return val * sgn;
        }

        public static double deg2rad(double p_y) 
        {
	        return p_y * Math.PI / 180.0;
        }

        public static double rad2deg(double p_y) 
        {
	        return p_y * 180.0 / Math.PI;
        }

        public static double ease(double p_x, double p_c) 
        {
	        if (p_c > 0) 
            {
		        return Math.Pow(p_x, p_c);
	        } 
            else if (p_c < 0) 
            {
		        //inout ease
		        if (p_x < 0.5) 
                {
			        return Math.Pow(p_x * 2.0, -p_c) * 0.5;
		        } 
                else 
                {
			        return (1.0 - Math.Pow(1.0 - (p_x - 0.5) * 2.0, -p_c)) *0.5 + 0.5;
		        }
	        } 
            else
		        return 0; // no ease (raw)
        }

        public static double fposmod(double p_x,double p_y) 
        {
	        if (p_x >= 0) 
            {
		        return Math.IEEERemainder(p_x , p_y);
	        } 
            else 
            {
		        return p_y - Math.IEEERemainder(-p_x, p_y); 
	        }
        }

        public static double lerp(double a, double b, double c) 
        {
		    return a + (b - a) * c;
	    }

        public static double stepify(double p_value, double p_step) 
        {
	        if (p_step != 0) 
            {
		        p_value = Math.Floor( p_value / p_step + 0.5 ) * p_step;		
	        }	
	        return p_value;
        }

        public static T clamp<T>(T m_a,T m_min,T m_max) where T : IComparable<T>
        {
            return m_a.CompareTo(m_min) < 0 ? m_min : ( m_a.CompareTo(m_max) > 0 ? m_max : m_a );
        }

        public static uint nearest_power_of_2(uint x) {
	        --x;
	        x |= x >> 1;
	        x |= x >> 2;
	        x |= x >> 4;
	        x |= x >> 8;
	        x |= x >> 16;
	        return ++x;
        }
        //=======================================================================================
        #endregion

        #region String

        public static string get_slice(this string p_str, string p_delim, int p_slice)
        {
            string[] delims = new string[] { p_str };
            string[] splitted = p_str.Split(delims, StringSplitOptions.None);
            if (splitted.Length <= p_slice)
                return "";
            else
                return splitted[p_slice];
        }

        public static string get_file(this string str)
        {
	        int sep = Math.Max( str.LastIndexOf("/"), str.LastIndexOf("\\") );
	        if (sep == -1)
		        return str;

	        return str.Substring(sep + 1);
        }

        public static string get_base_dir(this string str)
        {
            int basepos = str.IndexOf("://");
	        string rs = "";
	        string base_dir = "";
	        if (basepos != -1) 
            {
		        int end = basepos + 3;
		        rs = str.Substring(end, str.Length);
		        base_dir = str.Substring(0, end);
	        } 
            else 
            {
		        if (str.StartsWith("/")) 
                {
			        rs = str.Substring(1, str.Length);
			        base_dir = "/";
		        } 
                else 
                {
			        rs = str;
		        }
	        }

	        int sep = Math.Max( rs.LastIndexOf("/"), rs.LastIndexOf("\\") );
	        if (sep == -1)
		        return base_dir;

	        return base_dir + rs.Substring(0, sep);
        }

        public static bool is_resource_file(this String str)
        {
            return str.StartsWith("res://") && !str.Contains("::");
        }

        public static bool is_abs_path(this string str)
        {
            if (str.Length > 1)
		        return (str[0] == '/' || str[0] == '\\' || str.Contains(":/") || str.Contains(":\\"));
	        else if (str.Length == 1 )
		        return (str[0] == '/' || str[0] == '\\');
	        else
		        return false;
        }

        public static bool is_rel_path(this string str)
        {
            return !str.is_abs_path();
        }

        public static int hex_to_int(this String str)
        {
            int l = str.Length;
            if (l < 3)
                return 0;

            char[] s = str.ToArray();

            int sign_ptrpos = 0;

            int sign = s[sign_ptrpos + 0] == '-' ? -1 : 1;           

            if (sign < 0) 
            {
                sign_ptrpos++;
                l--;
                if (l < 2)
                    return 0;
            }

            if (s[sign_ptrpos + 0] != '0' || s[sign_ptrpos + 1] != 'x')
                   return 0;

            sign_ptrpos += 2;
            l -= 2;

            int hex = 0;

            while(s[sign_ptrpos] != 0) 
            {
                char c = s[sign_ptrpos].ToString().ToLower()[0];
                int n;
                if ( c >= '0' && c <= '9') 
                {
                    n = c -'0';
                } 
                else if ( c >= 'a' && c <= 'f') 
                {
                    n = (c - 'a') + 10;
                } 
                else 
                {
                    return 0;
                }

                hex *= 16;
                hex += n;
                sign_ptrpos++;
            }

            return hex * sign;

        }

        public static bool is_alpha_numeric(this char c)
        {
            return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
        }

        public static bool is_alphabet(this char c)
        {
            return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
        }

        public static bool is_numeric(this char c)
        {
            return (c >= '0' && c <= '9');
        }

        public static bool is_valid_identifier(this string p_str) 
        {
	        int len = p_str.Length;

	        if (len == 0)
		        return false;

	        for(int i = 0; i < len; i++) 
            {
		        if (i == 0) 
                {
			        if (p_str[0] >= '0' && p_str[0] <= '9')
				        return false; // no start with number plz
		        }

		        bool valid_char = (p_str[i] >= '0' && p_str[i] <= '9') 
                    || (p_str[i] >= 'a' && p_str[i] <= 'z') || (p_str[i] >= 'A' && p_str[i] <= 'Z') 
                    || p_str[i] == '_';

		        if (!valid_char)
			        return false;

	        }

	        return true;
        }

        public static bool is_valid_integer(this string p_str) 
        {
            try
            {
                int number = int.Parse(p_str);
                return true;
            }
            catch (Exception ex)
            {
                return false;
            }
        }

        public static bool is_valid_float(this string p_str)
        {
            try
            {
                double number = double.Parse(p_str);
                return true;
            }
            catch (Exception ex)
            {
                return false;
            }
        }

        public static bool is_valid_html_color(this string p_str)
        {
            return Color.html_is_valid(p_str);
        }

        public static bool is_valid_ip_address(this string p_str) 
        {
	        string[] ip = p_str.Split(new char[] { '.' });
	        if (ip.Length != 4)
		        return false;
	        
            for(int i = 0; i < ip.Length; i++) 
            {
		        string n = ip[i];
		        if (!n.is_valid_integer())
			        return false;
		        int val = int.Parse(n);
		        if (val < 0 || val > 255)
			        return false;
	        }
	        return true;
        }

        public static string strip_edges(this string p_str)
        {
            char[] tc = new char[32];
            for (int i = 0; i <= 32; i++) tc[i] = (char)i;
            return p_str.Trim(tc);
        }

        public static string xml_escape(this string p_str, bool p_escape_quotes = false) 
        {
	        string str = p_str;
	        str = str.Replace("&","&amp;");
	        str = str.Replace("<","&gt;");
	        str = str.Replace(">","&lt;");
	        if (p_escape_quotes) 
            {
		        str = str.Replace("'","&apos;");
		        str = str.Replace("\"","&quot;");
	        }
	        return str;
        }

        public static string xml_unescape(this string p_str)
        {
            string str = p_str;
            
            str = str.strip_edges();
            //str=str.replace("\"","");
            str = str.Replace("&gt;", "<");
            str = str.Replace("&lt;", ">");
            str = str.Replace("&apos;", "'");
            str = str.Replace("&quot;", "\"");
            /*
            for (int i=1;i<32;i++) {

                char chr[2]={i,0};
                str=str.replace("&#"+String::num(i)+";",chr);
            }*/
            str = str.Replace("&amp;", "&");

            return str;
        }

        public static string percent_encode(this string p_str)
        {
            byte[] cs = Encoding.UTF8.GetBytes(p_str);
            StringBuilder sb = new StringBuilder();
            for(int i = 0; i< cs.Length; i++) 
            {
		        byte c = cs[i];
		        if ((c >= 'A' && c <= 'Z') 
                || (c >= 'a' && c <= 'z') 
                || (c >= '0' && c <= '9') 
                || c == '-' || c == '_' || c == '~' || c == '.') 
                {
			        //char p[2] = {c,0};
			        sb.Append(c);
		        }    
                else 
                {
                    byte[] p = { (byte)'%', (byte)0, (byte)0, (byte)0 };

                    byte[] hex = { (byte)'0', (byte)'1', (byte)'2', (byte)'3', 
                                (byte)'4', (byte)'5', (byte)'6', (byte)'7', 
                                (byte)'8', (byte)'9', (byte)'a', (byte)'b', 
                                (byte)'c', (byte)'d', (byte)'e', (byte)'f' };

			        p[1] = hex[c >> 4];
			        p[2] = hex[c & 0xF];

                    foreach (byte b in p) sb.Append(b);
		        }
            }
            return sb.ToString();
        }

        public static string percent_decode(this string p_str)
        {
            List<byte> pe = new List<byte>();

	        for(int i = 0; i < p_str.Length; i++) 
            {
		        byte c = (byte)p_str[i];
		        if (c == '%' && i < p_str.Length -2) 
                {
			        byte a = (byte)char.ToLower(p_str[i + 1]);
			        byte b = (byte)char.ToLower(p_str[i + 2]);

			        c = 0;
			        if ( a >= '0' && a <= '9')
				        c = (byte)((a - '0') << 4);
			        else if ( a >= 'a' && a <= 'f')
				        c = (byte)((a - 'a' + 10) << 4);
			        else
				        continue;

			        byte d = 0;

			        if (b >= '0' && b <= '9')
				        d = (byte)(b - '0');
			        else if (b >= 'a' && b <= 'f')
				        d = (byte)(b - 'a' + 10);
			        else
				        continue;
			        c += d;
			        i += 2;
		        }
		        pe.Add(c);
	        }

	        pe.Add(0);

            return Encoding.UTF8.GetString(pe.ToArray());
        }

        public static string pad_decimals(this string p_str, int p_digits)
        {
	        string s = p_str;
	        int c = s.IndexOf(".");

	        if (c == -1) 
            {
		        if (p_digits <= 0) 
                {
			        return s;
		        }

		        s += ".";
		        c = s.Length - 1;
	        } 
            else 
            {
		        if (p_digits <= 0) 
                {
			        return s.Substring(0,c);
		        }
	        }

	        if (s.Length - (c + 1) > p_digits) 
            {
		        s = s.Substring(0, c + p_digits + 1);
	        } 
            else 
            {
		        while(s.Length - (c + 1) < p_digits) 
                {
			        s += "0";
		        }
	        }
	        return s;
        }

        public static string pad_zeros(this string p_str, int p_digits)
        {
	        string s = p_str;
	        int end = s.IndexOf(".");

	        if (end == -1) {
		        end = s.Length;
	        }

	        if (end == 0)
		        return s;

	        int begin = 0;

	        while (begin < end && (s[begin] < '0' || s[begin] > '9'))
		        begin++;

	        if (begin >= end)
		        return s;


	        while(end - begin < p_digits) 
            {
		        s = s.Insert(begin,"0");
		        end++;
	        }

	        return s;
        }

        public static string plus_file(this string p_str, string p_arg)
        {
            if (p_str.Length > 0 && p_str[p_str.Length - 1] == '/')
                p_str = p_str + p_arg;
            else
                p_str = p_str + "/" + p_arg;
            return p_str;
        }

        public static string simplify_path(this string p_str)
         {
	        string s = p_str;
	        string drive = "";
	        if (s.StartsWith("local://")) 
            {
		        drive = "local://";
		        s = s.Substring(8);
	        } 
            else if (s.StartsWith("res://")) 
            {
		        drive = "res://";
		        s = s.Substring(6);
	        } 
            else if (s.StartsWith("user://")) 
            {
		        drive = "user://";
		        s = s.Substring(6);
	        } 
            else if (s.StartsWith("/") || s.StartsWith("\\")) 
            {
		        drive = s.Substring(0,1);
		        s = s.Substring(1, s.Length - 1);
	        } 
            else 
            {
		        int p = s.IndexOf(":/");
		        if (p == -1)
			        p = s.IndexOf(":\\");
		        if (p != -1 && p < s.IndexOf("/")) 
                {
			        drive = s.Substring(0, p + 2);
			        s = s.Substring(p + 2);
		        }
	        }

	        s = s.Replace("\\","/");
	        List<string> dirs = new List<string>(s.Split(new char[]{'/'}, StringSplitOptions.RemoveEmptyEntries));
            
	        for(int i = 0; i < dirs.Count; i++) 
            {
		        String d = dirs[i];
		        if (d == ".") 
                {
                    dirs.RemoveAt(i);
			        i--;
		        } 
                else if (d == "..") 
                {
			        if (i == 0) 
                    {
				        dirs.RemoveAt(i);
				        i--;
			        } else {
				        dirs.RemoveAt(i);
				        dirs.RemoveAt(i - 1);
				        i -= 2;
			        }
		        }
	        }

	        s = "";

	        for(int i = 0; i < dirs.Count; i++)	 
            {
		        if (i > 0)
			        s += "/";
		        s += dirs[i];
	        }

	        return drive + s;
        }

        public static List<float> split_floats(this string p_str, string p_splitter, bool p_allow_empty)
        {
	        List<float> ret = new List<float>();
	        int from = 0;
	        int len = p_str.Length;

	        while(true) {

		        int end = p_str.IndexOf(p_splitter, from);
		        if (end < 0)
			        end = len;
		        if (p_allow_empty || (end > from))
			        ret.Add(float.Parse(p_str.Substring(from,end-from)));

		        if (end == len)
			        break;

		        from = end + p_splitter.Length;
	        }

	        return ret;
        }
        #endregion

        public static int find_nearest<TK, TV>(this Dictionary<TK, TV> dict, TK p_val,ref bool r_exact) where TK : IComparable<TK>
        {
		    r_exact = false;
		    if (dict.Count <= 0)
			    return 0;

		    int low = 0;
		    int high = dict.Count -1;
		    int middle = 0;
            KeyValuePair<TK, TV>[] a = dict.ToArray();

		    while( low <= high )
		    {
			    middle = ( low  + high ) / 2;

			    if( p_val.CompareTo(a[middle].Key) < 0 ) {
				    high = middle - 1; //search low end of array
			    } else if ( a[middle].Key.CompareTo(p_val) < 0) {
				    low = middle + 1; //search high end of array
			    } else {
				    r_exact=true;
				    return middle;
			    }
		    }

		    //return the position where this would be inserted
		    if (a[middle].Key.CompareTo(p_val) < 0)
			    middle++;
		    return middle;
	    }

        public static void swap<T>(ref T x, ref T y)
        {
            T aux = x;
            x = y;
            y = aux;
        }

        public static T memnew<T>(T p_obj)
        {            
            Godot.core.Object.postinitialize_handler(p_obj as Object);
            return p_obj;
        }

        public static void memdelete<T>(T p_obj) where T : class
        {
            if (!Godot.core.Object.predelete_handler(p_obj as Object))
		        return; // doesn't want to be deleted
            p_obj = null;
        }

        public static SysMethodInfo FindMethod(this System.Type p_type, string p_func)
        {
            return p_type.GetMethod(p_func, SysBindingFlags.Instance | SysBindingFlags.Static | SysBindingFlags.Public | SysBindingFlags.NonPublic | SysBindingFlags.FlattenHierarchy);
        }

        public static SysBindingFlags BindingFlags_Greedy = SysBindingFlags.Instance | SysBindingFlags.Static | SysBindingFlags.Public | SysBindingFlags.NonPublic | SysBindingFlags.FlattenHierarchy;
    }
}
